#include <scsi/scsi.h>

#include "iscsi.h"
#include "iscsi_dbg.h"
#include "ua.h"

#define ua_hashfn(lun)	((lun % UA_HASH_LEN))
#define ua_asc(ua) ((ua)->asc)
#define ua_ascq(ua) ((ua)->ascq)


static struct ua_entry * alloc_ua(void){
	struct ua_entry * ua;

	ua = kzalloc(sizeof(struct ua_entry), GFP_KERNEL );

	if(!ua){
		return NULL;
	}
	return ua;
}

static int free_ua(struct ua_entry * ua){
	
	assert(!ua);

	kfree(ua);
	return 0;
}

static int create_ua(
	struct ua_entry ** ua,
	u32 lun,
	u8 asc, 
	u8 ascq
){
	struct ua_entry * tmpua = NULL;
	
	tmpua = alloc_ua();
	if(tmpua == NULL){
		return -ENOMEM;
	}

	tmpua->lun = lun;
	tmpua->asc =asc;
	tmpua->ascq =ascq;
	*ua = tmpua;
	return 0;

}

/*ua_hash_lock should be hold*/
static struct ua_entry * ua_find_hash(
	struct iscsi_session *session,
	u32 lun,
	u8 asc	
){
	struct ua_entry *ua;
	struct list_head *uahead;
	int found;

	found = 0;
	
	uahead = &session->ua_hash[ua_hashfn(lun)];

	list_for_each_entry(ua,uahead,ua_list){
		if(ua->lun == lun ){
			if( (asc != UA_ASC_ALL && ua_asc(ua) == asc)
				|| asc == UA_ASC_ALL){
				found = 1;
				break;
			}			
		}
	}

	return found==1?ua:NULL;

}

/*ua_hash_lock should be hold*/
static void ua_insert_hash(
	struct iscsi_session *session,
	struct ua_entry *ua
){
	struct list_head * uahead;

	uahead = &session->ua_hash[ua_hashfn(ua->lun)];

	list_add_tail(&ua->ua_list, uahead);

	return;
}

/*ua_hash_lock should be hold*/
static void ua_remove_hash(
	struct iscsi_session *session,
	struct ua_entry *ua
){
	struct ua_entry * tmpua, *curua;
	struct list_head *uahead;
	
	uahead = &session->ua_hash[ua_hashfn(ua->lun)];

	list_for_each_entry_safe(curua,tmpua,uahead,ua_list){
		if(curua == ua){
			list_del(&curua->ua_list);
			return;
		}

	}

	return;

}


int ua_pending(
	struct iscsi_session *session,
	u32 lun,
	u8 asc

){
	struct ua_entry * tmpua;
	if(session == NULL || lun > UA_MAX_LUN){
		return -EINVAL;
	}

	spin_lock(&session->ua_hash_lock);
	tmpua = ua_find_hash(session, lun, asc);
	spin_unlock(&session->ua_hash_lock);
	
	if(tmpua == NULL){
		return 0;
	}

	return 1;	

}


int ua_generate (
	struct iscsi_session *session,
	u8 asc,
	u8 ascq,
	u32  lun,
	int rule
){
	struct ua_entry * ua = NULL;
	struct iscsi_session *tmpsession;
	struct list_head *sessionhead;
	int err;
	if(session == NULL || lun > UA_MAX_LUN){
		return -EINVAL;
	}

	if(!UA_INITIATOR_RULE_VALID(rule)){
		return -EINVAL;
	}

	sessionhead = &(session->target->session_list);
	list_for_each_entry(tmpsession,sessionhead,list){
		if(strcmp(tmpsession->initiator,session->initiator) == 0){
			if(rule == UA_INITIATOR_ALL || rule == UA_INITIATOR_INCLUDE){
				err = create_ua(&ua,lun,asc,ascq);
				if(!err){
					return err;
				}
				spin_lock(&session->ua_hash_lock);
				ua_insert_hash(tmpsession,ua);
				spin_unlock(&session->ua_hash_lock);
			}

		}
		else{//other initiator of the target
			if(rule == UA_INITIATOR_ALL || rule == UA_INITIATOR_EXCLUDE){
				err = create_ua(&ua,lun,asc,ascq);
				if(!err){
					return err;
				}
				spin_lock(&session->ua_hash_lock);
				ua_insert_hash(tmpsession,ua);
				spin_unlock(&session->ua_hash_lock);
			}
		}
		
	}
	
	return 0;	

}

int ua_deliver_clear (
	struct iscsi_cmnd 	*cmd
){
	struct ua_entry * ua = NULL;
	struct iscsi_session *session = NULL; 
	if(cmd == NULL || cmd->lun->lun > UA_MAX_LUN){
		return -EINVAL;
	}

	session = cmd->conn->session;

	if(!ua_pending(session,cmd->lun->lun, UA_ASC_ALL)){
		return 0;
	}

	spin_lock(&session->ua_hash_lock);
	ua = ua_find_hash(session, cmd->lun->lun,UA_ASC_ALL);

	assert(ua == NULL);

	iscsi_cmnd_set_sense(cmd,UNIT_ATTENTION,ua_asc(ua),ua_ascq(ua));

	ua_remove_hash(session,ua);
	
	spin_unlock(&session->ua_hash_lock);
	
	free_ua(ua);
	
	return 0;

}

int ua_clear (
	struct iscsi_session * session,
	u32 lun,
	u8 asc,
	int all
){
	struct list_head * uahead;
	struct ua_entry *curua, *tmpua;
	if(session == NULL || lun > UA_MAX_LUN){
		return -EINVAL;
	}

	if(!ua_pending(session,lun, asc)){
		return 0;
	}

	spin_lock(&session->ua_hash_lock);
	
	uahead = &session->ua_hash[ua_hashfn(lun)];
	
	list_for_each_entry_safe(curua, tmpua,uahead,ua_list){
		if(curua->lun != lun){
			continue;
		}
		if( (asc != UA_ASC_ALL && ua_asc(curua) == asc)
			|| asc == UA_ASC_ALL){
			list_del(&curua->ua_list);
			free_ua(curua);
			if(all == UA_FIRST){
				break;
			}
		}
	}

	spin_unlock(&session->ua_hash_lock);
	
	return 0;
	
}



